﻿using System;
using System.Collections.Generic;

namespace Bookvar.ObjectModel.Actions
{
    /// <summary>
    /// This class takes care of the undo redo logic for a collection. It keeps track of the items for removal,
    /// items for addition and the removed items.
    /// </summary>
    /// <remarks>
    /// This class does NOT guarantee that the item order in the target collection will be kept.
    /// </remarks>
    /// <typeparam name="T"></typeparam>
    public class UndoRedoCollectionHelper<T>
    {
        private bool executed;
        private ICollection<T> itemsToAdd;
        private ICollection<T> itemsToRemove;
        private ICollection<T> removedItems;
        private ICollection<T> targetCollection;

        /// <summary>
        /// Initializes a new instance of the <see cref="UndoRedoCollectionHelper&lt;T&gt;"/> class.
        /// </summary>
        public UndoRedoCollectionHelper()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="UndoRedoCollectionHelper{T}"/> class.
        /// </summary>
        /// <param name="targetCollection">The target collection.</param>
        /// <exception cref="ArgumentNullException">if <paramref name="targetCollection"/> is null.</exception>
        public UndoRedoCollectionHelper( ICollection<T> targetCollection )
        {
            if ( targetCollection == null ) throw new ArgumentNullException( "targetCollection" );

            this.targetCollection = targetCollection;
        }

        /// <summary>
        /// Gets or sets the target collection.
        /// </summary>
        /// <value>The target.</value>
        /// <exception cref="ArgumentNullException">if value is null.</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public ICollection<T> Target
        {
            get { return targetCollection; }
            set
            {
                if ( value == null )
                {
                    throw new ArgumentNullException( "value" );
                }

                targetCollection = value;
            }
        }

        /// <summary>
        /// The items that should be added to the target collection.
        /// </summary>
        /// <value>The items that should be added to the target collection,</value>
        public ICollection<T> ItemsToAdd
        {
            get
            {
                if ( itemsToAdd == null )
                {
                    itemsToAdd = new List<T>();
                }

                return itemsToAdd;
            }
        }

        /// <summary>
        /// The items that should be removed from the target collection.
        /// </summary>
        /// <value>The items that should be removed from the target collection.</value>
        public ICollection<T> ItemsToRemove
        {
            get
            {
                if ( itemsToRemove == null )
                {
                    itemsToRemove = new List<T>();
                }

                return itemsToRemove;
            }
        }

        private ICollection<T> RemovedItems
        {
            get
            {
                if ( removedItems == null )
                {
                    removedItems = new List<T>();
                }
                return removedItems;
            }
        }

        /// <summary>
        /// Executes the helper - adds the items to add; removes the items to remove; and saves data for undo.
        /// </summary>
        /// <exception cref="InvalidOperationException">if the target collection is null; or if this method is called twice.</exception>
        public void Execute()
        {
            if ( executed )
            {
                throw new InvalidOperationException( "Execute should not be called twice in a row. " );
            }

            if ( targetCollection == null )
            {
                throw new InvalidOperationException( "targetCollection must not be null." );
            }

            if ( itemsToRemove != null )
            {
                foreach ( T toRemove in itemsToRemove )
                {
                    if ( targetCollection.Contains( toRemove ) )
                    {
                        targetCollection.Remove( toRemove );
                        RemovedItems.Add( toRemove );
                    }
                }
            }

            if ( itemsToAdd != null )
            {
                foreach ( T toAdd in itemsToAdd )
                {
                    targetCollection.Add( toAdd );
                }
            }

            executed = true;
        }

        /// <summary>
        /// Undoes the changes to the collection caused by <see cref="Execute"/>.
        /// </summary>
        /// <exception cref="InvalidOperationException">if this method is called before the collection is executed.</exception>
        public void Undo()
        {
            if ( !executed )
            {
                throw new InvalidOperationException( "Undo should not be called before Execute" );
            }

            if ( itemsToAdd != null )
            {
                foreach ( T toAdd in itemsToAdd )
                {
                    targetCollection.Remove( toAdd );
                }
            }

            if ( removedItems != null )
            {
                foreach ( T removed in removedItems )
                {
                    targetCollection.Add( removed );
                }

                removedItems.Clear();
            }

            executed = false;
        }
    }
}